package com.qing.common.util

import android.content.Context
import android.util.Log
import androidx.annotation.AnyThread
import androidx.annotation.IntRange
import androidx.annotation.WorkerThread
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.Closeable
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.nio.ByteBuffer
import java.text.SimpleDateFormat
import java.util.Locale
import java.util.concurrent.Executors
import java.util.concurrent.ThreadFactory
import java.util.zip.GZIPInputStream
import java.util.zip.GZIPOutputStream

object LogUtil {

    private const val TAG = "LogUtil"

    private var loggerImpl: Logger? = null

    private var isLogToLogcat = true

    private const val FLUSH_THRESHOLD = 4096 // 写入缓冲大小

    fun setLoggerImpl(logger: Logger) {
        loggerImpl = logger
    }

    fun setIsLogToLogcat(isLogToLogcat: Boolean) {
        this.isLogToLogcat = isLogToLogcat
    }

    /**
     * 设置堆栈跟踪的深度, 默认为5 [android.util.Log.WARN]
     */
    fun setStackTraceLevel(
        @IntRange(
            from = Log.VERBOSE.toLong(),
            to = Log.ASSERT.toLong()
        ) level: Int
    ) {
        loggerImpl?.setStackTraceLevel(level)
    }

    fun d(tag: String, msg: String) {
        d(tag, msg, null)
    }

    fun d(tag: String, msg: String, throwable: Throwable? = null) {
        loggerImpl?.d(
            tag,
            buildMsg(msg),
            throwable
        )
        if (isLogToLogcat) {
            Log.d(
                tag, msg, throwable
            )
        }
    }

    fun i(tag: String, msg: String) {
        i(tag, msg, null)
    }

    fun i(tag: String, msg: String, throwable: Throwable? = null) {
        loggerImpl?.i(
            tag,
            buildMsg(msg),
            throwable
        )
        if (isLogToLogcat) {
            Log.i(
                tag, msg, throwable
            )
        }
    }

    fun w(tag: String, msg: String) {
        w(tag, msg, null)
    }

    fun w(tag: String, msg: String, throwable: Throwable? = null) {
        loggerImpl?.w(
            tag,
            buildMsg(msg),
            throwable
        )
        if (isLogToLogcat) {
            Log.w(
                tag, msg, throwable
            )
        }
    }

    fun e(tag: String, msg: String) {
        e(tag, msg, null)
    }

    fun e(tag: String, msg: String, throwable: Throwable? = null) {
        loggerImpl?.e(
            tag,
            buildMsg(msg),
            throwable
        )
        if (isLogToLogcat) {
            Log.e(
                tag, msg, throwable
            )
        }
    }

    fun createDefaultLogger(context: Context): Logger {
        return BaseLoggerImpl(context)
    }

    fun releaseLoggerImpl() {
        loggerImpl?.close()
    }

    fun decompressLog(path: String, outPath: String) {
        FileUtils.createFile(outPath).outputStream(true).use { outputStream ->
            // 分片读取，并写入到缓存文件
            FileInputStream(path).use { inputStream ->
                val buffer = ByteArray(FLUSH_THRESHOLD)
                while (true) {
                    val len = inputStream.read(buffer)
                    if (len == -1) {
                        break
                    }
                    outputStream.write(ByteBuffer.wrap(buffer, 0, len).decompress().array())
                }
            }
        }
    }

    interface Logger : AutoCloseable {
        /**
         * 设置日志文件路径，请先确保路径存在且有写入权限
         *
         * 默认日志文件写入到 Android/data/包名/cache/logs/log.gz
         * @param logPath 日志文件路径, 如为空, 则使用默认路径
         */
        fun setLogPath(logPath: String?)

        /**
         * 设置堆栈跟踪的深度, 默认为5 [android.util.Log.WARN]
         */
        fun setStackTraceLevel(level: Int)
        fun v(tag: String, msg: String, throwable: Throwable? = null)
        fun d(tag: String, msg: String, throwable: Throwable? = null)
        fun i(tag: String, msg: String, throwable: Throwable? = null)
        fun w(tag: String, msg: String, throwable: Throwable? = null)
        fun e(tag: String, msg: String, throwable: Throwable? = null)
    }

    private class BaseLoggerImpl(private val context: Context) : Logger {

        private var logPath: String? = null
        private var stackTraceLevel = Log.WARN
        private val logBuffer = StringBuilder()
        private val flushThreshold = FLUSH_THRESHOLD

        private val logChannel by lazy {
            if (logPath != null) {
                FileUtils.getLogFile(logPath!!)
            } else {
                FileUtils.getExternalLogFile(context)
            }.outputStream(true).channel
        }

        private val threadFactory by lazy {
            ThreadFactory { r ->
                val t = Thread(r, "BaseLoggerImpl")
                t.uncaughtExceptionHandler = Thread.UncaughtExceptionHandler { t, e ->
                    Log.e(TAG, "uncaught exception t=${t.name}, 文件写入失败", e)
                }
                t
            }
        }

        private val writeExecutor by lazy {
            Executors.newSingleThreadExecutor(threadFactory)
        }

        override fun setLogPath(logPath: String?) {
            this.logPath = logPath
        }

        override fun setStackTraceLevel(level: Int) {
            // check level invalidate
            if (level !in Log.VERBOSE..Log.ASSERT) {
                throw IllegalArgumentException("stackTraceLevel must be between Log.VERBOSE and Log.ASSERT")
            }
            stackTraceLevel = level
        }

        override fun v(tag: String, msg: String, throwable: Throwable?) {
            val traces = if (isAtLeastLevel(Log.VERBOSE)) {
                Debug.getCallers(throwable = throwable, depth = 2)
            } else {
                arrayOf()
            }
            write(buildLogEntry(tag, Log.VERBOSE, msg, traces))
        }

        override fun d(tag: String, msg: String, throwable: Throwable?) {
            val traces = if (isAtLeastLevel(Log.DEBUG)) {
                Debug.getCallers(throwable = throwable, depth = 2)
            } else {
                arrayOf()
            }
            write(buildLogEntry(tag, Log.DEBUG, msg, traces))
        }

        override fun i(tag: String, msg: String, throwable: Throwable?) {
            val traces = if (isAtLeastLevel(Log.INFO)) {
                Debug.getCallers(throwable = throwable, depth = 2)
            } else {
                arrayOf()
            }
            write(buildLogEntry(tag, Log.INFO, msg, traces))
        }

        override fun w(tag: String, msg: String, throwable: Throwable?) {
            val traces = if (isAtLeastLevel(Log.WARN)) {
                Debug.getCallers(throwable = throwable, depth = 2)
            } else {
                arrayOf()
            }
            write(buildLogEntry(tag, Log.WARN, msg, traces))
        }

        override fun e(tag: String, msg: String, throwable: Throwable?) {
            val traces = if (isAtLeastLevel(Log.ERROR)) {
                Debug.getCallers(throwable = throwable, depth = 2)
            } else {
                arrayOf()
            }
            write(buildLogEntry(tag, Log.ERROR, msg, traces))
        }

        @AnyThread
        private fun write(buffer: String) {
            writeExecutor.submit {
                logBuffer.append(buffer)
                if (logBuffer.length >= flushThreshold) {
                    flushBuffer()
                }
            }
        }

        @WorkerThread
        private fun flushBuffer() {
            val data = logBuffer.toString().toByteArray()
            logBuffer.clear()
            logChannel.write(ByteBuffer.wrap(data).compress())
        }

        private fun buildLogEntry(
            tag: String, level: Int, msg: String, traces: Array<String>
        ): String {
            val logString = when {
                traces.isNotEmpty() -> {
                    "${formatDate(System.currentTimeMillis())} ${logLevelToString(level)} $tag: $msg stackTrace: ${traces[Debug.INDEX_CLASS_NAME]}#${traces[Debug.INDEX_METHOD_NAME]}(${traces[Debug.INDEX_FILE_NAME]}:${traces[Debug.INDEX_LINE_NUMBER]})\n"
                }

                else -> {
                    "${formatDate(System.currentTimeMillis())} ${logLevelToString(level)} $tag: $msg\n"
                }
            }

            return logString
        }

        override fun close() {
            writeExecutor.submit {
                if (logBuffer.isNotEmpty()) {
                    flushBuffer()
                }
                logChannel.close()
            }
        }

        private fun isAtLeastLevel(level: Int): Boolean {
            return level >= stackTraceLevel
        }
    }
}

fun File.outputStream(append: Boolean): FileOutputStream =
    FileOutputStream(/* file = */ this, /* append = */ append)

fun ByteBuffer.compress(): ByteBuffer {
    val outputStream = ByteArrayOutputStream()
    GZIPOutputStream(outputStream).use {
        val bytes = ByteArray(remaining())
        get(bytes)
        it.write(bytes)
    }
    return ByteBuffer.wrap(outputStream.toByteArray())
}

fun ByteBuffer.decompress(): ByteBuffer {
    val bytes = ByteArray(remaining())
    get(bytes)
    return ByteBuffer.wrap(bytes).let {
        GZIPInputStream(ByteArrayInputStream(it.array())).readBytes()
    }.let {
        ByteBuffer.wrap(it)
    }
}

fun logLevelToString(
    @IntRange(
        from = Log.VERBOSE.toLong(),
        to = Log.ASSERT.toLong()
    ) level: Int
): String {
    return when (level) {
        Log.VERBOSE -> "V"
        Log.DEBUG -> ""
        Log.INFO -> "I"
        Log.WARN -> "W"
        Log.ERROR -> "E"
        Log.ASSERT -> "A"
        else -> "U"
    }
}

fun formatDate(date: Long): String {
    return SimpleDateFormat("yyyy-MM-dd HH:mm:sss", Locale.getDefault()).format(date)
}

fun buildMsg(msg: String): String {
    return "$msg - t: ${Thread.currentThread().name}, p: ${android.os.Process.myPid()}"
}